package com.example.gridfsdemo;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileNameUtil;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.*;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.mongodb.client.gridfs.model.GridFSUploadOptions;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * @author susc
 * @title: GridfsService
 * @projectName gridfs-demo
 * @description: TODO
 * @date 2022/8/2510:27
 */
@Service
@Slf4j
public class GridfsService {

//    @Autowired
//    private SimpleMongoClientDatabaseFactory mongodbfactory;

    private String dbName = "gj_dev";

    @Autowired
    private MongoClient mongoClient;
    @Autowired
    private GridFsTemplate gridFsTemplate;

    /**
     * 创建 GridFS 存储桶
     * 当您调用 时create()，如果存储桶不存在，MongoDB 不会创建它。相反，MongoDB 会根据需要创建存储桶，例如在您上传第一个文件时。
     *
     * @return
     */
    public GridFSBucket makeBucket(){
//        MongoDatabase mongoDatabase = mongodbfactory.getMongoDatabase(dbName);
        MongoDatabase database = mongoClient.getDatabase(dbName);
        GridFSBucket gridFSBucket = GridFSBuckets.create(database);
//        要使用除默认名称以外的自定义名称创建或引用存储桶 fs，请将您的存储桶名称作为第二个参数传递给该create() 方法，如下所示：
//        GridFSBucket gridFSBucket2 = GridFSBuckets.create(database, "myCustomBucket");
        return gridFSBucket;
    }

    /**
     * 上传本地文件（使用输入流上传附件）
     * @param filePath
     * @throws FileNotFoundException
     */
    public void uploadLocalFile(String filePath) throws FileNotFoundException {
        GridFSBucket gridFSBucket = makeBucket();
        try (InputStream streamToUploadFrom = new FileInputStream(filePath) ) {
            GridFSUploadOptions options = new GridFSUploadOptions()
                    .chunkSizeBytes(1048576)
                    .metadata(new Document("type", "zip archive"));

            ObjectId fileId = gridFSBucket.uploadFromStream("myProject.zip", streamToUploadFrom, options);
            System.out.println("The file id of the uploaded file is: " + fileId.toHexString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 上传附件（使用输入流上传附件）
     * @param file
     */
    public void uploadFile2(MultipartFile file)  {
        GridFSBucket gridFSBucket = makeBucket();
        Integer chunkSizeBytes = (int) file.getSize();
        try (InputStream inputStream = file.getInputStream()){
            GridFSUploadOptions options = new GridFSUploadOptions()
                    .chunkSizeBytes(chunkSizeBytes)
                    .metadata(new Document("type", "zip archive"));
            String fileName = FileNameUtil.getName(file.getOriginalFilename());
            ObjectId fileId = gridFSBucket.uploadFromStream(fileName, inputStream, options);
            System.out.println("The file id of the uploaded file is: " + fileId.toHexString());
        }catch (IOException e){
            log.error(e.getMessage(), e);
        }
    }

    public void uploadFile(MultipartFile file) throws IOException {
        ObjectId objectId = gridFsTemplate.store(file.getInputStream(), file.getOriginalFilename(), StandardCharsets.UTF_8);
        System.out.println(objectId);
    }

    /**
     * 使用输出流上传文件
     * 将文件系统中名为“project.zip”的文件读入字节数组。
     *
     * 使用 设置块大小GridFSUploadOptions。
     *
     * 设置一个名为type“zip archive”的自定义元数据字段。
     *
     * 将字节写入 a GridFSUploadStream，分配文件名“myProject.zip”。流将数据读入缓冲区，直到达到chunkSize设置中指定的限制，并将其作为新块插入chunks集合中。
     * notes:
     * 如果您的文件上传不成功，则该操作会引发异常，并且任何上传的块都将成为孤立块。孤立块是 GridFS 集合中的文档，chunks它不引用 GridFS 集合中的任何文件 ID files。当上传或删除操作中断时，文件块可能成为孤立块。要删除孤立的块，您必须使用读取操作识别它们并使用写入操作删除它们。
     */
    public void uploadFileByOutputSteam(String filePath) throws IOException {
        byte[] data = Files.readAllBytes(Paths.get(filePath));
        GridFSBucket gridFSBucket = makeBucket();
        GridFSUploadOptions options = new GridFSUploadOptions()
                .chunkSizeBytes(1048576)
                .metadata(new Document("type", "zip archive"));

        try (GridFSUploadStream uploadStream = gridFSBucket.openUploadStream("myProject.zip", options)) {
            uploadStream.write(data);
            uploadStream.flush();
            System.out.println("The file id of the uploaded file is: " + uploadStream.getObjectId().toHexString());
        } catch (Exception e) {
            System.err.println("The file upload failed: " + e);
        }
    }


    /**
     * 检索文件信息
     * 检索存储在 filesGridFS 存储桶集合中的文件元数据
     * 1.文件的id
     * 2.文件名
     * 3.文件的长度/大小
     * 4.上传日期和时间
     *
     * metadata您可以在其中存储任何其他信息的文档
     * 要从 GridFS 存储桶中检索文件，请在实例上调用该find()方法。GridFSBucket该方法返回 a GridFSFindIterable ，您可以从中访问结果。
     *
     * 以下代码示例向您展示如何从 GridFS 存储桶中的所有文件中检索和打印文件元数据。
     */
    public List<GridFSFile> queryAllFileMetadata(){
        List<GridFSFile> fileList = new ArrayList<>();
        GridFSBucket gridFSBucket = makeBucket();
        GridFSFindIterable gridFSFiles = gridFSBucket.find();
        MongoCursor<GridFSFile> iterator = gridFSFiles.iterator();
        while (iterator.hasNext()){
            fileList.add(iterator.next());
        }
        return fileList;
    }


    /**
     * 通过objectId下载附件
     * @param objectId
     */
    public void download(String objectId, HttpServletResponse response) throws UnsupportedEncodingException {
        Query query = Query.query(Criteria.where("_id").is(objectId));
        GridFSFile gridFSFile = gridFsTemplate.findOne(query);
        // 解决文件下载的时候，中文乱码的问题
        String name = new String(gridFSFile.getFilename().getBytes("GBK"), "ISO-8859-1");
        response.setHeader("Content-Disposition", "attachment;fileName=" + name);
        GridFSDownloadStream gridFSDownloadStream = makeBucket().openDownloadStream(gridFSFile.getObjectId());
        //创建gridFsResource，用于获取流对象
        GridFsResource gridFsResource = new GridFsResource(gridFSFile, gridFSDownloadStream);
        InputStream inputStream = null;
        try {
            inputStream = gridFsResource.getInputStream();
            IoUtil.copy(inputStream, response.getOutputStream());
        }catch (Exception e){
            log.error(e.getMessage(), e);
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
